
#pragma once

#include "..\\ExternalTools\\ExternalTools.h"

#include "..\\Loirey\\loirey_GLOBAL.h"
#include "..\\Loirey\\loirey_MyMath.h"
#include "..\\Loirey\\loirey_BasicStructure.h"

using namespace loirey;

class CDenseSingleDimensionIntegralImage
{
public:
	int W, H;
	size_t WH;
protected:
	int _W1, _H1;
	int _W2, _H2;
	size_t _W1H1, _W2H2;
	CImg<double> _imgEnlarged;
	CImg<double> _img_Vx;
	CImg<double> _img_Hy;
	CImg<double> _img_Sum;
protected:
	const double* _base_enlarged;
	const double* _base_Vx;
	const double* _base_Hy;
	const double* _base_x0y0;

public:
	CDenseSingleDimensionIntegralImage();
	virtual void ClearContent();
	virtual size_t GetMemoryUsage() const;
protected:
	void _InitIntegralFunctions();
public:
	bool TranslateCoordinate(
		int& new_ix, int& new_iy,
		double& new_fx, double& new_fy,
		double old_x, double old_y) const;
public:
	double GetPixel(double x, double y) const;
	double GetIntegral(double x, double y) const;
	double GetAverage(double center_x, double center_y, double width, double height) const;
public:
	template<typename T>
	void myInit(const CImg<T>& SourceImage, int z, int v)
	{
		W = SourceImage.width; H = SourceImage.height; WH = W * H;
		const T* qBase = SourceImage.data + WH * z + WH * SourceImage.depth * v;
		_W1 = W + 1; _H1 = H + 1; _W1H1 = _W1 * _H1;
		_W2 = W + 2; _H2 = H + 2; _W2H2 = _W2 * _H2;
		_imgEnlarged.assign(_W2, _H2, 1, 1);
		int ix, iy;
		const T* qd;
		double* pd;
		for ( iy = 0; iy < (int)H; ++iy )
		{
			qd = qBase + 0 + iy * W;
			pd = _imgEnlarged.data + 0 + (iy + 1) * _W2;
			*(pd++) = (double)(*qd);
			for ( ix = 0; ix < (int)W; ++ix )
				*(pd++) = (double)(*(qd++));
			*pd = *(pd-1);
		}
		pd = _imgEnlarged.data + 0 + 0 * _W2;
		for ( ix = 0; ix < (int)_W2; ++ix )
		{
			*pd = pd[_W2];
			++pd;
		}
		pd = _imgEnlarged.data + 0 + H * _W2;
		for ( ix = 0; ix < (int)_W2; ++ix )
		{
			pd[_W2] = *pd;
			++pd;
		}

		_InitIntegralFunctions();
	}
};

class CDenseImage
{
public:
	static const int EXPANDING_MODE_COLOR = 0;
	static const int EXPANDING_MODE_TILE = 1;
	static const int EXPANDING_MODE_EDGE = 2;
public:
	CSimpleTypeArray<CDenseSingleDimensionIntegralImage> dsdiiList;
public:
	int W, H, V;
	size_t WH;
	CSimpleTypeArray<double> DefaultColor;
	double OutExtent;

public:
	CDenseImage()
	{
		ClearContent();
	}
	virtual void ClearContent()
	{
		W = H = V = 0;
		WH = 0;
		dsdiiList.DeepClear();
		DefaultColor.DeepClear();
		OutExtent = -1.0;
	}
	virtual size_t GetMemoryUsage() const
	{
		size_t ret = sizeof(int) * 4 + dsdiiList.GetMemoryUsage() + DefaultColor.GetMemoryUsage();
		for ( size_t i = 0; i < dsdiiList.Size; ++i )
			ret += dsdiiList.pElement[i].GetMemoryUsage();
		return ret;
	}
public:
	// return false if should use DefaultColor
	virtual bool ProcessCoordinate(double& DstX, double& DstY, double x, double y, int ExpandingMode) const
	{
		if (OutExtent > -EPS)
		{
			if (x<0-OutExtent || x>W+OutExtent || y<0-OutExtent || y>H+OutExtent)
				return false;
		}

		DstX = DstY = 0.0;

		if (ExpandingMode == EXPANDING_MODE_TILE)
		{
			DstX = x - W * CMyMath::TranslateNumber_Floor(x / W);
			DstY = y - H * CMyMath::TranslateNumber_Floor(y / H);
			DstX = (DstX<0 ? 0 : (DstX>W-EPS ? W-EPS : DstX));
			DstY = (DstY<0 ? 0 : (DstY>H-EPS ? H-EPS : DstY));
		}
		else if (ExpandingMode == EXPANDING_MODE_EDGE)
		{
			DstX = (x<0 ? 0 : (x>W-EPS ? W-EPS : x));
			DstY = (y<0 ? 0 : (y>H-EPS ? H-EPS : y));
		}
		else if (ExpandingMode == EXPANDING_MODE_COLOR)
		{
			if (x<0 || x>W || y<0 || y>H)
				return false;
			DstX = (x<0 ? 0 : (x>W-EPS ? W-EPS : x));
			DstY = (y<0 ? 0 : (y>H-EPS ? H-EPS : y));
		}
		else
		{
			//TODO
		}
		return true;
	}
	virtual double GetPixel(double x, double y, int v, int ExpandingMode) const
	{
		if (V < 1) return 0.0;

		int ix, iy;
		double fx, fy;
		double ret;

		bool pr = ProcessCoordinate(x, y, x, y, ExpandingMode);
		if (!pr) return DefaultColor.pElement[v];
		ix = CMyMath::TranslateNumber_Floor(x); fx = x - ix;
		iy = CMyMath::TranslateNumber_Floor(y); fy = y - iy;
		//if (OutExtent>-EPS)
		//{
		//	if (x<0-OutExtent || x>W+OutExtent || y<0-OutExtent || y>H+OutExtent)
		//		return DefaultColor.pElement[v];
		//}
		//ix = CMyMath::TranslateNumber_Floor(x); fx = x - ix;
		//iy = CMyMath::TranslateNumber_Floor(y); fy = y - iy;
		//if (ExpandingMode == EXPANDING_MODE_TILE)
		//{
		//	ix = (ix%W+W)%W;
		//	iy = (iy%H+H)%H;
		//}
		//else if (ExpandingMode == EXPANDING_MODE_EDGE)
		//{
		//	x = (x<0 ? 0 : (x>W-EPS ? W-EPS : x));
		//	y = (y<0 ? 0 : (y>H-EPS ? H-EPS : y));
		//	ix = CMyMath::TranslateNumber_Floor(x); fx = x - ix;
		//	iy = CMyMath::TranslateNumber_Floor(y); fy = y - iy;
		//}
		//else if (ExpandingMode == EXPANDING_MODE_COLOR)
		//{
		//	if ((ix<0 || ix>=W || iy<0 || iy>=H))
		//		return DefaultColor.pElement[v];
		//}
		//else
		//{
		//	//TODO
		//}
		//x = ix + fx;
		//y = iy + fy;

		ret = dsdiiList.pElement[v].GetPixel(x, y);
		return ret;
	}
protected:
	virtual void _AfterSetImage()
	{
	}
public:
	void myInit(string strFN_SourceImage_IntRGB)
	{
		CImg<int> imgSrcRGB;
		CExternalTools::myLoadImage(strFN_SourceImage_IntRGB, imgSrcRGB);
		myInit(imgSrcRGB);
	}
public:
	template<typename T>
	void myInit(const CImg<T>& imgSource, int z = 0)
	{
		W = imgSource.width;
		H = imgSource.height;
		V = imgSource.dim;

		WH = W * H;
		
		dsdiiList.Resize(V);
		int iv;
		for ( iv = 0; iv < V; ++iv )
			dsdiiList.pElement[iv].myInit(imgSource, z, iv);

		DefaultColor.Resize(V);
		DefaultColor.Fill(0.0);
		OutExtent = -1.0;

		this->_AfterSetImage();
	}
};

class CCoordinateTransformer
{
public:
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const = 0;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const = 0;
public:
	virtual void WorkOnBox(CRectBox<double>& DstRectBox, CRectBox<double> SrcRectBox) const
	{
		double t;
		this->Work(DstRectBox.w, DstRectBox.h, SrcRectBox.x+SrcRectBox.w, SrcRectBox.y+SrcRectBox.h);
		this->Work(DstRectBox.x, DstRectBox.y, SrcRectBox.x, SrcRectBox.y);
		if (DstRectBox.x < DstRectBox.w)
			DstRectBox.w -= DstRectBox.x;
		else
		{
			t = DstRectBox.x;
			DstRectBox.x = DstRectBox.w;
			DstRectBox.w = t - DstRectBox.w;
		}
		if (DstRectBox.y < DstRectBox.h)
			DstRectBox.h -= DstRectBox.y;
		else
		{
			t = DstRectBox.y;
			DstRectBox.y = DstRectBox.h;
			DstRectBox.h = t - DstRectBox.h;
		}
	}
	virtual void InverseOnBox(CRectBox<double>& DstRectBox, CRectBox<double> SrcRectBox) const
	{
		double t;
		this->Inverse(DstRectBox.w, DstRectBox.h, SrcRectBox.x+SrcRectBox.w, SrcRectBox.y+SrcRectBox.h);
		this->Inverse(DstRectBox.x, DstRectBox.y, SrcRectBox.x, SrcRectBox.y);
		if (DstRectBox.x < DstRectBox.w)
			DstRectBox.w -= DstRectBox.x;
		else
		{
			t = DstRectBox.x;
			DstRectBox.x = DstRectBox.w;
			DstRectBox.w = t - DstRectBox.w;
		}
		if (DstRectBox.y < DstRectBox.h)
			DstRectBox.h -= DstRectBox.y;
		else
		{
			t = DstRectBox.y;
			DstRectBox.y = DstRectBox.h;
			DstRectBox.h = t - DstRectBox.h;
		}
	}
};

class CZoomConfig : public CCoordinateTransformer
{
public:
	double xScale, yScale;
	double xCenterOld, yCenterOld;
	double xCenterNew, yCenterNew;

public:
	CZoomConfig(double Scale = 1.0)
	{
		myInit(Scale);
	}
	CZoomConfig(double xScale, double yScale)
	{
		myInit(xScale, yScale);
	}
	CZoomConfig(
		double xScale, double yScale,
		double xCenterOld, double yCenterOld,
		double xCenterNew, double yCenterNew)
	{
		myInit(xScale, yScale, xCenterOld, yCenterOld, xCenterNew, yCenterNew);
	}
public:
	virtual void myInit(double Scale)
	{
		myInit(Scale, Scale, 0.0, 0.0, 0.0, 0.0);
	}
	virtual void myInit(double xScale, double yScale)
	{
		myInit(xScale, yScale, 0.0, 0.0, 0.0, 0.0);
	}
	virtual void myInit(
		double xScale, double yScale,
		double xCenterOld, double yCenterOld,
		double xCenterNew, double yCenterNew)
	{
		if (abs(xScale) < EPS) xScale = 1.0;
		if (abs(yScale) < EPS) yScale = 1.0;
		this->xScale = xScale;
		this->yScale = yScale;
		this->xCenterOld = xCenterOld;
		this->yCenterOld = yCenterOld;
		this->xCenterNew = xCenterNew;
		this->yCenterNew = yCenterNew;
	}
	virtual void Clear()
	{
		myInit(1.0);
	}
public:
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const
	{
		NewX = xCenterNew + (OldX - xCenterOld) * xScale;
		NewY = yCenterNew + (OldY - yCenterOld) * yScale;
	}
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const
	{
		OldX = xCenterOld + (NewX - xCenterNew) / xScale;
		OldY = yCenterOld + (NewY - yCenterNew) / yScale;
	}
};

class CZoomableImage : public CDenseImage
{
protected:
	void _AfterSetImage() { }
public:
	template<typename T>
	CZoomConfig BuildImage(CImg<T>& imgDestination, double xScale, double yScale, int ExpandingMode, int v = -1) const
	{
		if (abs(xScale)<EPS) xScale = 1.0;
		if (abs(yScale)<EPS) yScale = 1.0;
		int Width = CMyMath::TranslateNumber_Round(W * abs(xScale), (int)0);
		int Height = CMyMath::TranslateNumber_Round(H * abs(yScale), (int)0);
		
		CZoomConfig ret(xScale, yScale, 0.0, 0.0,
			(xScale<0 ? W * abs(xScale) : 0),
			(yScale<0 ? H * abs(yScale) : 0));
		//CZoomConfig ret(xScale, yScale, W * 0.5, H * 0.5, Width * 0.5, Height * 0.5);
		BuildImage(imgDestination, Width, Height, ret, ExpandingMode, v);
		return ret;
	}
	template<typename T>
	void BuildImage(CImg<T>& imgDestination, int Width, int Height, const CZoomConfig& ZoomConfig, int ExpandingMode, int v = -1) const
	{
		int iva;
		if (v<0 || v>=V)
			iva = V;
		else
			iva = 1;
		if (imgDestination.width!=Width || imgDestination.height!=Height || imgDestination.depth!=1 || imgDestination.dim!=iva)
			imgDestination.assign(Width, Height, 1, iva);
		BuildImage(imgDestination.data, Width, Height, ZoomConfig, ExpandingMode, v);
	}
	template<typename T>
	void BuildImage(T* pDstData, int Width, int Height, const CZoomConfig& ZoomConfig, int ExpandingMode, int v = -1) const
	{
		int iv_amount, iv_base, iv, iv_real;
		int fake_w = CMyMath::TranslateNumber_Round(W * ZoomConfig.xScale, (int)0); if (fake_w < 1) fake_w = 1;
		int fake_h = CMyMath::TranslateNumber_Round(H * ZoomConfig.yScale, (int)0); if (fake_h < 1) fake_h = 1;
		if (v<0 || v>=V)
		{	iv_amount = V;	iv_base = 0;	}
		else
		{	iv_amount = 1;	iv_base = v;	}

		int ix, iy;
		double tx, ty, tt;
		bool pr;
		double zx = 1.0 / ZoomConfig.xScale;
		double zy = 1.0 / ZoomConfig.yScale;
		T* rd = pDstData;
		for ( iv = 0; iv < iv_amount; ++iv )
		{
			iv_real = iv_base + iv;
			const CDenseSingleDimensionIntegralImage& tDSI = this->dsdiiList.pElement[iv_real];
			for ( iy = 0; iy < Height; ++iy ) for ( ix = 0; ix < Width; ++ix )
			{
				tx = ix + 0.5; ty = iy + 0.5;
				ZoomConfig.Inverse(tx, ty, tx, ty);
				pr = this->ProcessCoordinate(tx, ty, tx, ty, ExpandingMode);
				if (!pr)
					tt = DefaultColor.pElement[iv_real];
				else
					tt = tDSI.GetAverage(tx, ty, zx, zy);
				*(rd++) = CMyMath::TranslateNumber_Round(tt, (T)0);
			}
		}

		//CRectBox<double> dBox;
		//dBox.x = dBox.y = 0.0;
		//dBox.w = Width;
		//dBox.h = Height;
		//ZoomConfig.InverseOnBox(dBox, dBox);
		//dBox.w += dBox.x;
		//dBox.h += dBox.y;
		//if (dBox.x < 0) dBox.x = 0;
		//if (dBox.w > W) dBox.w = W;
		//if (dBox.y < 0) dBox.y = 0;
		//if (dBox.h > H) dBox.h = H;
		//ZoomConfig.WorkOnBox(dBox, dBox);

		//int ix, iy;
		//double tx, ty, tt;
		//double* pd;
		//CImg<double> imgTemp;
		//int tw = CMyMath::TranslateNumber_Round(dBox.w, (int)0);
		//int th = CMyMath::TranslateNumber_Round(dBox.h, (int)0);
		//imgTemp.assign(tw, th, 1, iv_amount);

		//double zx = 1.0 / ZoomConfig.xScale;
		//double zy = 1.0 / ZoomConfig.yScale;
		//for ( iv = 0; iv < iv_amount; ++iv )
		//{
		//	iv_real = iv_base + iv;
		//	const CDenseSingleDimensionIntegralImage& tDSI = this->dsdiiList.pElement[iv_real];
		//	pd = imgTemp.data + ((size_t)tw) * ((size_t)th) * (size_t)iv;
		//	for ( iy = 0; iy < th; ++iy ) for ( ix = 0; ix < tw; ++ix )
		//	{
		//		tx = dBox.x + ix + 0.5;
		//		ty = dBox.y + iy + 0.5;
		//		ZoomConfig.Inverse(tx, ty, tx, ty);
		//		*(pd++) = tDSI.GetAverage(tx, ty, zx, zy);
		//	}
		//}

		////CExternalTools::mySaveImage("aaa.jpg", imgTemp);
		//
		//CDenseImage DenseImage;
		//DenseImage.myInit(imgTemp);

		//T* rd = pDstData;
		//for ( iv = 0; iv < iv_amount; ++iv ) for ( iy = 0; iy < Height; ++iy ) for ( ix = 0; ix < Width; ++ix )
		//{
		//	//if (ix+0.5-dBox.x>0 && iy+0.5-dBox.y>0)
		//	//	iv = iv;
		//	tt = DenseImage.GetPixel(ix + 0.5 - dBox.x, iy + 0.5 - dBox.y, iv, ExpandingMode);
		//	*(rd++) = CMyMath::TranslateNumber_Round(tt, (T)0);
		//}
	}
};

class C_ImgTran : public CCoordinateTransformer
{
public:
	static const int TYPE_COMPOSITE = -1;
	static const int TYPE_TRANSLATE = 0;
	static const int TYPE_ROTATE = 1;
	static const int TYPE_SCALE = 2;
protected:
	int _Type;

public:
	virtual ~C_ImgTran();
	virtual C_ImgTran* myClone() const;
	virtual void ToInverseTransformation();
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const;
};

class CImageTransformation : public C_ImgTran
{
public:
	CSimpleTypeArray<C_ImgTran*> TranList;

public:
	CImageTransformation();
	void Clear();
	void PushBack(C_ImgTran* pNewTransformation);
public:
	void CloneDataFrom(const CImageTransformation& AnotherInstance);
	CImageTransformation(const CImageTransformation& AnotherInstance);
	CImageTransformation& operator = (const CImageTransformation& AnotherInstance);
public:
	virtual ~CImageTransformation();
	virtual C_ImgTran* myClone() const;
	virtual void ToInverseTransformation();
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const;
};

class CTransformableImage : public CDenseImage
{
protected:
	void _AfterSetImage() { }
public:
	CTransformableImage()
	{
	}
public:
	template<typename T>
	void ComputeTransformedSize(T& LowX, T& LowY, T& HighX, T& HighY, const CImageTransformation& Transformation) const
	{
		double lx, ly, hx, hy, tx, ty;
		Transformation.Work(tx, ty, 0, 0);
		hx = lx = tx;
		hy = ly = ty;
		Transformation.Work(tx, ty, W, 0);
		if (tx < lx) lx = tx; else if (tx > hx) hx = tx;
		if (ty < ly) ly = ty; else if (ty > hy) hy = ty;
		Transformation.Work(tx, ty, 0, H);
		if (tx < lx) lx = tx; else if (tx > hx) hx = tx;
		if (ty < ly) ly = ty; else if (ty > hy) hy = ty;
		Transformation.Work(tx, ty, W, H);
		if (tx < lx) lx = tx; else if (tx > hx) hx = tx;
		if (ty < ly) ly = ty; else if (ty > hy) hy = ty;
		LowX = CMyMath::TranslateNumber_Round(lx, (T)0);
		LowY = CMyMath::TranslateNumber_Round(ly, (T)0);
		HighX = CMyMath::TranslateNumber_Round(hx, (T)0);
		HighY = CMyMath::TranslateNumber_Round(hy, (T)0);
	}
	template<typename T>
	void NormalizeTransformedSize(T& Width, T& Height, CImageTransformation& Transformation) const
	{
		double lx, ly, hx, hy;
		ComputeTransformedSize(lx, ly, hx, hy, Transformation);
		Transformation.PushBack(new C_ImgTran_Translate(-lx, -ly));
		Width = CMyMath::TranslateNumber_Round(hx - lx, (T)0);
		Height = CMyMath::TranslateNumber_Round(hy - ly, (T)0);
	}
public:
	template<typename T>
	void BuildImage(CImg<T>& imgDestination, int Width, int Height, const CImageTransformation& Transformation, int ExpandingMode, int v = -1) const
	{
		int iva;
		if (v<0 || v>=V)
			iva = V;
		else
			iva = 1;
		if (imgDestination.width!=Width || imgDestination.height!=Height || imgDestination.depth!=1 || imgDestination.dim!=iva)
			imgDestination.assign(Width, Height, 1, iva);
		BuildImage(imgDestination.data, Width, Height, Transformation, ExpandingMode, v);
	}
	template<typename T>
	void BuildImage(T* pDstData, int Width, int Height, const CImageTransformation& Transformation, int ExpandingMode, int v = -1) const
	{
		int iv_base, iv_amount, iv, iv_real, ix, iy;
		double tx,ty,t;
		T *pd;

		if (v<0 || v>=V)
		{	iv_amount = V;	iv_base = 0;	}
		else
		{	iv_amount = 1;	iv_base = v;	}

		pd = pDstData;
		for ( iv = 0; iv < iv_amount; ++iv )
		{
			iv_real = iv_base + iv;
			for ( iy = 0; iy < Height; ++iy ) for ( ix = 0; ix < Width; ++ix )
			{
				Transformation.Inverse(tx, ty, ix + 0.5, iy + 0.5);
				t = GetPixel(tx, ty, iv_real, ExpandingMode);
				*(pd++) = CMyMath::TranslateNumber_Round(t, (T)0);
			}
		}
	}
};

class C_ImgTran_Translate : public C_ImgTran
{
public:
	double dx, dy;

public:
	C_ImgTran_Translate();
	C_ImgTran_Translate(double dx, double dy);
	C_ImgTran_Translate(double OldX, double OldY, double NewX, double NewY);
	virtual void myInit(double dx, double dy);
	virtual void myInit(double OldX, double OldY, double NewX, double NewY);
public:
	virtual ~C_ImgTran_Translate();
	virtual C_ImgTran* myClone() const;
	virtual void ToInverseTransformation();
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const;
};

class C_ImgTran_Rotate : public C_ImgTran
{
public:
	double cx, cy, rot;
	double cos_r, sin_r;

public:
	C_ImgTran_Rotate();
	C_ImgTran_Rotate(double CenterX, double CenterY, double Rotate);
	virtual void myInit(double CenterX, double CenterY, double Rotate);
public:
	virtual ~C_ImgTran_Rotate();
	virtual C_ImgTran* myClone() const;
	virtual void ToInverseTransformation();
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const;
};

class C_ImgTran_Scale : public C_ImgTran
{
public:
	double cx, cy, sx, sy;

public:
	C_ImgTran_Scale();
	
	//C_ImgTran_Scale(double Scale);
	//C_ImgTran_Scale(double ScaleX, double ScaleY);
	//virtual void myInit(double Scale);
	//virtual void myInit(double ScaleX, double ScaleY);
	C_ImgTran_Scale(double CenterX, double CenterY, double Scale);
	C_ImgTran_Scale(double CenterX, double CenterY, double ScaleX, double ScaleY);
	virtual void myInit(double CenterX, double CenterY, double Scale);
	virtual void myInit(double CenterX, double CenterY, double ScaleX, double ScaleY);
public:
	virtual ~C_ImgTran_Scale();
	virtual C_ImgTran* myClone() const;
	virtual void ToInverseTransformation();
	virtual void Work(double& NewX, double& NewY, double OldX, double OldY) const;
	virtual void Inverse(double& OldX, double& OldY, double NewX, double NewY) const;
};

